﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.IO;
using Microsoft.Xna.Framework.Input;

namespace Prospectus
{
    public class TileLayer
    {
        #region Variables and Properties

        private Block[,] blocks;
        //private Block finishArea;

        Random rand = new Random();

        public Player Player { get { return player; } }
        Player player;
        private Vector2 start;
        
        List<AnimatedSprite> renderList = new List<AnimatedSprite>();

        // Map content
        public ContentManager Content { get { return content; } }
        ContentManager content;

        /// <summary>
        /// Game1 reference to give the tilelayer access to things
        /// such as the GraphicsDevice, ScreenWidth, and ScreenHeight.
        /// </summary>
        public Game1 GameRef
        {
            get { return gameRef; }
            private set { gameRef = value; }
        }
        private Game1 gameRef;

        /// <summary>
        /// Width of the map measured in blocks
        /// </summary>
        public int Width { get { return blocks.GetLength(0); } }
        /// <summary>
        /// Height of the map measured in blocks
        /// </summary>
        public int Height { get { return blocks.GetLength(1); } }

        /// <summary>
        /// Width of the map measured in pixels
        /// </summary>
        public int WidthInPixels { get { return Width * Engine.BlockWidth; } }
        /// <summary>
        /// Height of the map measured in pixels
        /// </summary>
        public int HeightInPixels { get { return Height * Engine.BlockHeight; } }

        public Dictionary<string, Block> blocksDictionary = new Dictionary<string, Block>();

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor using the Content Pipeline
        /// </summary>
        public TileLayer(bool isNewGame, Game1 gameRef)
        {
            GameRef = gameRef;
            content = gameRef.Content;
            
            CreateBlockDictionary();

            if (isNewGame)
                LoadContent();
            //else
            //    LoadContent(fileStream);
        }

        #endregion

        #region Create Block Dictionary Region

        private void CreateBlockDictionary()
        {
            //finishArea = new Block(
            //            Content.Load<Texture2D>("Tiles/block"),
            //            BlockType.Finish,
            //            'f');

            // Dictionary method should speed loading times when playing game

            BlockMaterial material = BlockMaterial.Coal;
            float strength = 0.0f;

            string[] blockTypes = { "coal", "dirt", "iron", "plutonium", "rock", "silver", "uranium" };
            string[] blockEdges = { "Center", "Column", "Corner", "Edge", "End", "Single" };
            int textureX = 0, textureY = 0;

            for (int y = 0; y < blockTypes.Length; y++)
            {
                for (int x = 0; x < blockEdges.Length; x++)
                {
                    if (y == 0)
                    {
                        material = BlockMaterial.Coal;
                        strength = 50.0f;
                    }
                    if (y == 1)
                    {
                        material = BlockMaterial.Dirt;
                        strength = 30.0f;
                    }
                    if (y == 2)
                    {
                        material = BlockMaterial.Iron;
                        strength = 60.0f;
                    }
                    if (y == 3)
                    {
                        material = BlockMaterial.Plutonium;
                        strength = 80.0f;
                    }
                    if (y == 4)
                    {
                        material = BlockMaterial.Rock;
                        strength = 45.0f;
                    }
                    if (y == 5)
                    {
                        material = BlockMaterial.Silver;
                        strength = 70.0f;
                    }
                    if (y == 6)
                    {
                        material = BlockMaterial.Uranium;
                        strength = 90.0f;
                    }
                    
                    // TOP ROTATION
                    blocksDictionary.Add(
                        blockTypes.GetValue(y).ToString() + blockEdges.GetValue(x).ToString() + "Top",
                        new Block(
                            Content.Load<Texture2D>("Tiles/blockSheet"),
                            blockTypes[y],
                            material,
                            BlockType.Impassable,
                            char.Parse(blockTypes.GetValue(y).ToString().Substring(0, 1)),
                            0.0f,
                            new Rectangle(textureX, textureY, Engine.BlockWidth, Engine.BlockHeight),
                            new Vector2(Engine.BlockWidth / 2, Engine.BlockHeight / 2),
                            strength));
                    // RIGHT ROTATION
                    blocksDictionary.Add(
                        blockTypes.GetValue(y).ToString() + blockEdges.GetValue(x).ToString() + "Right",
                        new Block(
                            Content.Load<Texture2D>("Tiles/blockSheet"),
                            blockTypes[y],
                            material,
                            BlockType.Impassable,
                            char.Parse(blockTypes.GetValue(y).ToString().Substring(0, 1)),
                            MathHelper.ToRadians(90.0f),
                            new Rectangle(textureX, textureY, Engine.BlockWidth, Engine.BlockHeight),
                            new Vector2(Engine.BlockWidth / 2, Engine.BlockHeight / 2),
                            strength));
                    // BOTTOM ROTATION
                    blocksDictionary.Add(
                        blockTypes.GetValue(y).ToString() + blockEdges.GetValue(x).ToString() + "Bottom",
                        new Block(
                            Content.Load<Texture2D>("Tiles/blockSheet"),
                            blockTypes[y],
                            material,
                            BlockType.Impassable,
                            char.Parse(blockTypes.GetValue(y).ToString().Substring(0, 1)),
                            MathHelper.ToRadians(180.0f),
                            new Rectangle(textureX, textureY, Engine.BlockWidth, Engine.BlockHeight),
                            new Vector2(Engine.BlockWidth / 2, Engine.BlockHeight / 2),
                            strength));
                    // LEFT ROTATION
                    blocksDictionary.Add(
                        blockTypes.GetValue(y).ToString() + blockEdges.GetValue(x).ToString() + "Left",
                        new Block(
                            Content.Load<Texture2D>("Tiles/blockSheet"),
                            blockTypes[y],
                            material,
                            BlockType.Impassable,
                            char.Parse(blockTypes.GetValue(y).ToString().Substring(0, 1)),
                            MathHelper.ToRadians(270.0f),
                            new Rectangle(textureX, textureY, Engine.BlockWidth, Engine.BlockHeight),
                            new Vector2(Engine.BlockWidth / 2, Engine.BlockHeight / 2),
                            strength));

                    textureX += Engine.BlockWidth;
                    if (textureX >= 128)
                    {
                        textureX = 0;
                        textureY += Engine.BlockHeight;
                    }
                }
            }

            blocksDictionary.Add("blank", new Block(null, BlockType.Passable, '.'));
        }

        #endregion

        #region New Load and Create Region

        public void LoadContent()
        {
            // NOTES: 06/14 Current Terrain Generation relies
            // on map being mostly square.
            blocks = new Block[512, 256];
            Block[,] tempBlocks = new Block[Width, Height];

            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    blocks[x, y] = blocksDictionary["blank"];
                    tempBlocks[x, y] = blocksDictionary["blank"];
                }
            }

            int[] blox = new int[Width];

            blox = CreatePerlinNoise();
            
            // Smooth out the top terrain by setting each y to the average
            // of the previous y and following y
            for (int i = 0; i < Width - 1; i++)
            {
                if (i == 0) { blox[i] = (blox[i] + blox[i + 1]) / 2; }
                else if (i == Width - 1) { blox[i] = (blox[i - 1] + blox[i]) / 2; }
                else { blox[i] = (blox[i - 1] + blox[i] + blox[i + 1]) / 3; }
            }

            int levelGround = Height / 10;


            // Place blocks horizontally based on the Perlin Noise values
            for (int x = 0; x < Width; x++)
                blocks[x, levelGround + blox[x]] = blocksDictionary["dirtSingleTop"];

            // Fill in the rest of the blocks below the surface
            // Mostly dirt at this point
            FillInMap();
            
            for (int y = 5; y < Height - 5; y++)
            {
                for (int x = 5; x < Width - 5; x++)
                {
                    // Percentage needs to be 60% to make 40% filled
                    // NOTES 06/10: Perlin Noise method functional and not bad,
                    // "Poking holes" cave creation method pretty good.
                    // NOTES 06/14: Making the percentage 55% / 45% is pretty good
                    // with the addition of possibly skipping over the block if the 
                    // depth is too little
                    if (rand.Next(100) <= 25)
                    {
                        // ATTEMPTING TO PUT HOLES IN COMPLETELY FILLED MAP
                        // AS OPPOSED TO PUTTING BLOCKS IN EMPTY MAP
                        blocks[x, y] = LoadBlock('.', x, y);
                    }
                }
            }

            for (int i = 1; i < 6; i++)
                CreateCaves(i, tempBlocks);
                        
            for (int x = (Width / 2) - 15; x < (Width / 2) + 15; x++)
            {
                for (int y = (Height / 2) - 15; y < (Height / 2) + 15; y++)
                {
                    RemoveBlock(x, y);
                }
            }
            blocks[Width / 2, Height / 2] = LoadStartTile(Width / 2, Height / 2);

            //int finishX = rand.Next(Width - 20);
            //int finishY = rand.Next(Height - 20);
            //for (int x = finishX; x < finishX + 3; x++)
            //{
            //    for (int y = finishY; y < finishY + 5; y++)
            //    {
            //        RemoveBlock(x, y);
            //        blocks[x, y] = finishArea;
            //    }
            //}

            // Different edged pieces
            for (int y = 1; y < Height - 1; y++)
            {
                for (int x = 1; x < Width - 1; x++)
                {
                    if (blocks[x, y].Texture == null) continue;
                    
                    UpdateBlock(x, y);
                }
            }
        }

        /// <summary>
        /// Method for filling in the map with blocks. Handles placing of 
        /// special blocks with random chances of spawning.
        /// </summary>
        private void FillInMap()
        {
            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    //if (x == 0 || x == Width - 1) continue;
                    //if (y == 0 || y == Height - 1) continue;
                                        
                    if (blocks[x, y].Texture != null)
                    {
                        for (int j = y; j < Height; j++)
                        {
                            if (Height / 7 < j && j < Height / 5)
                            {
                                if (rand.Next(10) <= 5) { blocks[x, j] = blocksDictionary["rockSingleTop"]; }
                                else { blocks[x, j] = blocksDictionary["dirtSingleTop"]; }
                            }
                            else if (Height / 5 < j && j < Height / 2)
                            {
                                if (rand.Next(10) <= 8) { blocks[x, j] = blocksDictionary["rockSingleTop"]; }
                                else { blocks[x, j] = blocksDictionary["dirtSingleTop"]; }
                            }
                            else if (j > Height / 2)
                            {
                                if (rand.Next(100) <= 95) { blocks[x, j] = blocksDictionary["rockSingleTop"]; }
                                else { blocks[x, j] = blocksDictionary["dirtSingleTop"]; }
                            }
                            else
                            {
                                if (rand.Next(100) <= 95) { blocks[x, j] = blocksDictionary["dirtSingleTop"]; }
                                else { blocks[x, j] = blocksDictionary["rockSingleTop"]; }
                            }
                        }
                    }
                }
            }
        }

        private void CreateCaves(int i, Block[,] tempBlocks)
        {
            int neighboringWalls = 0;
            int nsewNeighbors = 0;
            string blockName = "";

                for (int y = 5; y < Height - 5; y++)
                {
                    for (int x = 5; x < Width - 5; x++)
                    {
                        if (blocks[x, y].Texture != null)
                        {
                            for (int j = y; j < Height - 5; j++)
                            {
                                if (blocks[x - 1, j - 1].Texture != null)
                                {
                                    blockName = blocks[x - 1, j - 1].Name;
                                    neighboringWalls++;
                                }
                                if (blocks[x, j - 1].Texture != null)
                                {
                                    blockName = blocks[x, j - 1].Name;
                                    nsewNeighbors++;
                                    neighboringWalls++;
                                }
                                if (blocks[x + 1, j - 1].Texture != null)
                                {
                                    blockName = blocks[x + 1, j - 1].Name;
                                    neighboringWalls++;
                                }
                                if (blocks[x + 1, j].Texture != null)
                                {
                                    blockName = blocks[x + 1, j].Name;
                                    nsewNeighbors++;
                                    neighboringWalls++;
                                }
                                if (blocks[x + 1, j + 1].Texture != null)
                                {
                                    blockName = blocks[x + 1, j + 1].Name;
                                    neighboringWalls++;
                                }
                                if (blocks[x, j + 1].Texture != null)
                                {
                                    blockName = blocks[x, j + 1].Name;
                                    nsewNeighbors++;
                                    neighboringWalls++;
                                }
                                if (blocks[x - 1, j + 1].Texture != null)
                                {
                                    blockName = blocks[x - 1, j + 1].Name;
                                    neighboringWalls++;
                                }
                                if (blocks[x - 1, j].Texture != null)
                                {
                                    blockName = blocks[x - 1, j].Name;
                                    nsewNeighbors++;
                                    neighboringWalls++;
                                }

                                if (i < 4)
                                {
                                    if (neighboringWalls > 4) { tempBlocks[x, j] = blocksDictionary[blockName + "SingleTop"]; }
                                    else tempBlocks[x, j] = LoadBlock('.', x, j);
                                }
                                else
                                {
                                    if (neighboringWalls > 7) { tempBlocks[x, j] = blocksDictionary[blockName + "SingleTop"]; }
                                    else tempBlocks[x, j] = LoadBlock('.', x, j);
                                }

                                neighboringWalls = 0;
                                nsewNeighbors = 0;
                            }
                        }
                    }
                }
            
            for (int y = 5; y < Height - 5; y++)
                for (int x = 5; x < Width - 5; x++)
                        blocks[x, y] = tempBlocks[x, y];
        }

        #endregion

        #region Perlin Noise Generation

        private int[] CreatePerlinNoise()
        {
            float[] values = new float[Width];
            int[] realValues = new int[Width];

            for (int i = 0; i < values.Length; i++)
            {
                values[i] = PerlinNoise(i);
                values[i] *= 5;

                // Trying to smooth out some of the noise
                if (i != 0 && i != values.Length - 1)
                {
                    if (values[i] + 2 > values[i - 1])
                    {
                        if (rand.Next() % 5 == 1)
                            values[i] = values[i - 1];
                    }
                    else if (values[i] + 2 < values[i - 1])
                    {
                        if (rand.Next() % 5 == 1)
                            values[i] = values[i - 1];
                    }
                }
                realValues[i] = (int)Math.Ceiling(values[i]);
            }

            return realValues;
        }

        private float Noise(int i, int x)
        {
            if (i == 0)
            {
                x = (x << 13) ^ x;
                return (float)(1.0 - ((x * (x * x * 557 + 1049) + 2411) & 0x7fffffff) / 1073741824.0);
            }
            else if (i == 1)
            {
                x = (x << 13) ^ x;
                return (float)(1.0 - ((x * (x * x * 1303 + 2473) + 3229) & 0x7fffffff) / 1073741824.0);
            }
            else if (i == 2)
            {
                x = (x << 13) ^ x;
                return (float)(1.0 - ((x * (x * x * 4441 + 6277) + 7549) & 0x7fffffff) / 1073741824.0);
            }
            else
            {
                x = (x << 13) ^ x;
                return (float)(1.0 - ((x * (x * x * 4663 + 6007) + 6961) & 0x7fffffff) / 1073741824.0);
            }
        }

        private float SmoothedNoise(int i, float x)
        {
            return Noise(i, (int)x) / 2 + Noise(i, (int)x - 1) / 4 + Noise(i, (int)x + 1) / 4;
        }

        private float InterpolatedNoise(int i, float x)
        {
            int y = (int)x;
            float fractionalX = x - y;

            float v1 = SmoothedNoise(i, y);
            float v2 = SmoothedNoise(i, y + 1);

            return MathHelper.Lerp(v1, v2, fractionalX);
        }

        private float PerlinNoise(float x)
        {
            float total = 0;
            float p = 0.5f; // persistance
            int n = 4; // four octaves

            for (int i = 0; i <= n; i++)
            {
                float frequency = 2 ^ i;
                float amplitude = (float)Math.Pow(p, i);

                total += InterpolatedNoise(i, x * frequency) * amplitude;
            }

            return total;
        }

        #endregion

        #region Old Create Terrain Method

        /// <summary>
        /// Recursive function for creating basic terrain. 
        /// Uses a custom version of the Midpoint Displacement Algorithm.
        /// As of 06/11 this version is Outdated. Now using Perlin Noise Method.
        /// </summary>
        private void CreateTerrain(Point leftPoint, Point rightPoint, int n, List<Point> tList)
        {
            if (n == 0)
                return;

            Point middlePoint = new Point((leftPoint.X + rightPoint.X) / 2, rand.Next(leftPoint.Y - 50, rightPoint.Y + 50));

            tList.Add(middlePoint);

            CreateTerrain(leftPoint, middlePoint, n - 1, tList);
            CreateTerrain(middlePoint, rightPoint, n - 1, tList);
        }

        #endregion

        #region Old Load and Save Code

        private void LoadContent(Stream fileStream)
        {
            string playerLine = "";
            int lineNumber = 0;

            // Load the level and ensure all of the lines are the same length.
            int width = 0;
            List<string> lines = new List<string>();
            using (StreamReader reader = new StreamReader(fileStream))
            {
                string line = reader.ReadLine();

                while (line != null)
                {
                    if (line.Contains("[Player Position]"))
                    {
                        playerLine = reader.ReadLine();
                        line = reader.ReadLine();
                    }
                    else
                    {
                        if (lineNumber == 0)
                            width = line.Length;

                        lines.Add(line);
                        if (line.Length != width)
                            throw new Exception(String.Format("The length of line {0} is different from all preceeding lines.", lines.Count));
                        line = reader.ReadLine();

                        lineNumber++;
                    }
                }
            }
            // Allocate the block grid.
            blocks = new Block[width, lines.Count];

            int playerX, playerY;
            String[] pL = playerLine.Split(' ');
            playerX = int.Parse(pL[0]);
            playerY = int.Parse(pL[1]);

            // Loop over every block position,
            for (int y = 0; y < Height; ++y)
            {
                for (int x = 0; x < Width; ++x)
                {
                    if (playerX == x && playerY == y)
                    {
                        blocks[x, y] = LoadBlock('o', x, y);
                    }
                    else
                    {
                        char blockType = lines[y][x];
                        blocks[x, y] = LoadBlock(blockType, x, y);
                    }
                }
            }

            // Verify that the level has a beginning and an end.
            if (Player == null)
                throw new NotSupportedException("A map must have a spawn point.");
        }

        private Block LoadBlock(char block, int x, int y)
        {
            switch (block)
            {
                // Blank space
                case '.':
                    return new Block(null, BlockType.Passable, '.');

                // Floating platform
                case 'p':
                    return LoadBlock("platform", BlockType.Platform, 'p');

                // Ground block
                case 'g':
                    return LoadBlock("dirtEdge1", BlockType.Impassable, 'g');

                // Player block
                case 'o':
                    return LoadStartTile(x, y);

                default:
                    throw new NotSupportedException(String.Format("Unsupported block type character '{0}' at position {1}, {2}.", block, x, y));
            }
        }

        private Block LoadBlock(string name, BlockType blockType, char mapData)
        {
            return new Block(Content.Load<Texture2D>("Tiles/" + name), blockType, mapData);
        }

        private Block LoadStartTile(int x, int y)
        {
            if (Player != null)
                throw new NotSupportedException("The map may only have one spawn point.");

            start = RectangleExtensions.GetBottomCenter(GetBlockBounds(x, y));
            player = new Player(this, start);
            renderList.Add(player);

            return new Block(null, BlockType.Passable, '.');
        }

        private void Save(string fileName)
        {
            Point playerPos = Engine.ConvertVectorToCell(new Vector2(
                player.Position.X + player.texture.Width / 2,
                player.Position.Y + player.texture.Height / 2));

            using (StreamWriter writer = new StreamWriter("Content/Saved Games/" + fileName))
            {
                writer.WriteLine("[Player Position]");
                string line = playerPos.X.ToString() + " " + playerPos.Y.ToString();
                writer.WriteLine(line);

                for (int y = 0; y < Height; ++y)
                {
                    line = string.Empty;

                    for (int x = 0; x < Width; ++x)
                    {
                        if (playerPos == new Point(x, y))
                            line += ".";
                        else
                            line += blocks[x, y].MapData.ToString();
                    }

                    writer.WriteLine(line);
                }
            }
        }

        #endregion

        #region Block Modifier Methods
        
        public void RemoveBlock(int x, int y)
        {
            blocks[x, y] = LoadBlock('.', x, y);
        }

        public void PlaceBlock(int x, int y, Block block)
        {
            if (blocks[x, y].Texture == null)
                blocks[x, y] = block;
        }

        public void UpdateBlock(int x, int y)
        {
            if (blocks[x, y].Texture == null || blocks[x, y].BlockType == BlockType.Passable || blocks[x, y].BlockMaterial == BlockMaterial.Empty)
                return;

            if (x == 0 || x == Width - 1)
                return;

            if (y == 0 || y == Height - 1)
                return;
            
            // SINGLE BLOCKS
            if (blocks[x, y - 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x - 1, y].BlockMaterial != blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "SingleTop"];
            }

            // TOP BLOCKS
            else if (blocks[x - 1, y].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial == blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "EdgeTop"];
            }

            // BOTTOM BLOCKS
            else if (blocks[x - 1, y].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial == blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "EdgeBottom"];
            }

            // RIGHT BLOCKS
            else if (blocks[x - 1, y].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial != blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "EdgeRight"];
            }

            // LEFT BLOCKS
            else if (blocks[x - 1, y].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial == blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "EdgeLeft"];
            }

            // TOP RIGHT CORNER BLOCKS
            else if (blocks[x - 1, y].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial != blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "CornerTop"];
            }

            // TOP LEFT CORNER BLOCKS
            else if (blocks[x - 1, y].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial == blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "CornerLeft"];
            }

            // BOTTOM RIGHT CORNER BLOCKS
            else if (blocks[x - 1, y].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial != blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "CornerRight"];
            }

            // BOTTOM LEFT CORNER BLOCKS
            else if (blocks[x - 1, y].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial == blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "CornerBottom"];
            }

            // VERTICAL COLUMN BLOCKS
            else if (blocks[x - 1, y].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial != blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "ColumnTop"];
            }

            // HORIZONTAL COLUMN BLOCKS
            else if (blocks[x - 1, y].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial == blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "ColumnRight"];
            }

            // TOP END BLOCKS
            else if (blocks[x - 1, y].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial != blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "EndTop"];
            }

            // BOTTOM END BLOCKS
            else if (blocks[x - 1, y].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial != blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "EndBottom"];
            }

            // RIGHT END BLOCKS
            else if (blocks[x - 1, y].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial != blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "EndRight"];
            }

            // LEFT END BLOCKS
            else if (blocks[x - 1, y].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial != blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial == blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "EndLeft"];
            }

            else if (blocks[x - 1, y].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y - 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x, y + 1].BlockMaterial == blocks[x, y].BlockMaterial &&
                blocks[x + 1, y].BlockMaterial == blocks[x, y].BlockMaterial)
            {
                blocks[x, y] = blocksDictionary[blocks[x, y].Name + "CenterTop"];
            }

        }

        #endregion

        #region Dispose Method

        public void Dispose()
        {
            Content.Unload();
        }

        #endregion

        #region Block Accessor Methods

        public BlockType GetBlockType(Point p)
        {
            // Prevent escaping past the level ends.
            if (p.X < 0 || p.X >= Width)
                return BlockType.Impassable;
            // Allow jumping past the level top and falling through the bottom.
            if (p.Y < 0 || p.Y >= Height)
                return BlockType.Impassable;

            return blocks[p.X, p.Y].BlockType;
        }

        public BlockType GetBlockType(int x, int y)
        {
            // Prevent escaping past the level ends.
            if (x < 0 || x >= Width)
                return BlockType.Impassable;
            // Allow jumping past the level top and falling through the bottom.
            if (y < 0 || y >= Height)
                return BlockType.Impassable;

            return blocks[x, y].BlockType;
        }

        /// <summary>
        /// Gets the bounding rectangle of a block in world space.
        /// </summary>
        public Rectangle GetBlockBounds(int x, int y)
        {
            return new Rectangle(x * Engine.BlockWidth + 3, y * Engine.BlockHeight + 3, Engine.BlockWidth - 6, Engine.BlockHeight - 6);
        }

        /// <summary>
        /// Block Accessor method to retrieve the block at a certain place
        /// in the map, usually for placing it in inventory.
        /// </summary>
        public Block GetBlock(int x, int y)
        {
            return blocks[x, y];
        }

        #endregion

        #region Update Region

        public void Update(GameTime gameTime, Camera camera)
        {
            //if (InputManager.IsKeyPress(Keys.P))
            //    Save("mapData.txt");

            Player.Update(gameTime, camera);
            
        }

        #endregion

        #region Draw Region

        public void Draw(SpriteBatch spriteBatch, Camera camera)
        {
            Point min = Engine.ConvertVectorToCell(camera.Position);
            Point max = Engine.ConvertVectorToCell(
                camera.Position + new Vector2(
                    spriteBatch.GraphicsDevice.Viewport.Width + Engine.BlockWidth,
                    spriteBatch.GraphicsDevice.Viewport.Height + Engine.BlockHeight));

            DrawBlocks(spriteBatch, camera, min, max);

            foreach (AnimatedSprite s in renderList)
            {
                s.Draw(spriteBatch);
            }

            //Player.Draw(spriteBatch);
        }

        /// <summary>
        /// Draws each block in the map
        /// </summary>
        private void DrawBlocks(SpriteBatch spriteBatch, Camera camera, Point min, Point max)
        {
            min.X = (int)Math.Max(min.X, 0);
            min.Y = (int)Math.Max(min.Y, 0);
            max.X = (int)Math.Min(max.X, Width);
            max.Y = (int)Math.Min(max.Y, Height);

            for (int x = min.X; x < max.X; x++)
            {
                for (int y = min.Y; y < max.Y; y++)
                {
                    // If there is a visible block in that position
                    Texture2D texture = blocks[x, y].Texture;
                    if (texture != null)
                    {
                        // Draw it in screen space.
                        Vector2 position = new Vector2(x, y) * Block.Size;
                        Vector2 blockOrigin = new Vector2(8, 8);
                        position += blockOrigin;
                        float depth = 0.5f;

                        if (blocks[x, y].BlockType == BlockType.Finish)
                            depth = 0f;

                        spriteBatch.Draw(
                            texture,
                            position,
                            blocks[x, y].SourceRectangle,
                            Color.White,
                            blocks[x, y].Rotation,
                            blockOrigin,
                            1.0f,
                            blocks[x, y].SpriteEffects,
                            depth);
                    }
                }
            }
        }

        #endregion
    }
}
